﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Configuration;
using Microsoft.WindowsAzure.StorageClient;
using crmbl.Azure.Globals;
using crmbl.Azure.Tables.Globals;
using crmbl.Azure.Metadata.Globals;

namespace crmbl.Azure.Diagnostics.PerformanceCounters.Processor
{
    public class PerformanceCounterProcessor : IDisposable
    {
        private const String PERFORMANCE_COUNTER_TABLENAME = "WADPerformanceCountersTable";

        String _storageAccountName;
        String _storageAccountKey;
        Guid _storageAccountId;

        int _flushToDatabaseCount;

        TableServiceContext _tableContext;

        ApplicationHelper _app;

        DataTable _toProcessData;

        #region Constructors

        public PerformanceCounterProcessor(Guid storageAccountId, String storageAccountName, String StorageAccountPrimaryKey, String targetDataSourceConnectionString, int flushToDatabaseCount = 1000)
        {
            try
            {
                _storageAccountName = storageAccountName;
                _storageAccountKey = StorageAccountPrimaryKey;

                _storageAccountId = storageAccountId;

                _flushToDatabaseCount = flushToDatabaseCount;

                // Constuct a new application helper
                _app = new ApplicationHelper(targetDataSourceConnectionString);

                // Initiate a new Table Context
                _tableContext = AzureTableHelper.GetTableContext(_storageAccountName, _storageAccountKey);
            }
            catch (Exception ex)
            {
                // Error in initializing PerformanceCounterProcessor
                Logger.WriteMessage(String.Format("Exception in initializing PerformanceCounterProcessor. PARAMETERS: {0}; EXCEPTION: {1}", Logger.FormatParametersToString("StorageAccountId", storageAccountId, "StorageAccountName", storageAccountName), ex.ToString()), EventLevel.Error, 30006);
            }
        }
        #endregion

        #region Public Methods

        public void Process()
        {
            try
            {
                // Connect to configuration database, get the latest id
                GenericTableEntity lastUpdatedConfig = _app.GetTableObjectMetadata(_storageAccountId, PERFORMANCE_COUNTER_TABLENAME);

                long maxPartitionKey = long.Parse(lastUpdatedConfig.PartitionKey);
                String maxRowKey = lastUpdatedConfig.RowKey;
                DateTime maxTimestamp = lastUpdatedConfig.Timestamp;

                foreach (PerformanceCounterEntity perfCounter in GetPerformanceCountersToProcess(lastUpdatedConfig.PartitionKey, lastUpdatedConfig.RowKey, lastUpdatedConfig.Timestamp))
                {
                    try
                    {
                        // Add the Performance Counter
                        AddDataToToProcess(perfCounter);

                        // Update if newer
                        if (perfCounter.Timestamp > maxTimestamp)
                        {
                            maxTimestamp = perfCounter.Timestamp;
                            maxPartitionKey = long.Parse(perfCounter.PartitionKey);
                            maxRowKey = perfCounter.RowKey;
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.WriteMessage(String.Format("Exception while processing performance entity to bulk insert cache.  PARAMETERS: {0}; EXCEPTION: {1}",Logger.FormatParametersToString("StorageAccountId", _storageAccountId, "StorageAccountName", _storageAccountName,"PerformanceCounterEntity",perfCounter.ToString()),ex.ToString()), EventLevel.Warning, 30204);
                    }
                }

                WriteToProcessDataToDatabase();               

                try
                {
                    // Update the TableDownload
                    _app.UpdateTableObjectUpdate(_storageAccountId, PERFORMANCE_COUNTER_TABLENAME, "0" + maxPartitionKey.ToString(), maxRowKey, maxTimestamp);
                }
                catch (Exception ex)
                {
                    Logger.WriteMessage(String.Format("Exception while updating table update history; table entities will be reprocessed.  PARAMETERS: {0}; EXCEPTION: {1}",Logger.FormatParametersToString("StorageAccountId", _storageAccountId, "StorageAccountName", _storageAccountName), ex.ToString()), EventLevel.Error, 30206);
                }
            }
            catch (Exception ex)
            {
                // Unhandled, unexpected error while processing performance counters
                Logger.WriteMessage(String.Format("Exception in processing PerformanceCounterProcessor. PARAMETERS: {0}; EXCEPTION: {1}", Logger.FormatParametersToString("StorageAccountId", _storageAccountId, "StorageAccountName", _storageAccountName), ex.ToString()), EventLevel.Error, 30106);
            }
        }

        #endregion

        #region Bulk Data Methods

        private void AddDataToToProcess(PerformanceCounterEntity perfCounter)
        {
            if (_toProcessData == null)
                BuildToProcessDataTable();

            
            object[] o = {
                             _app.GetPerformanceCounterId(perfCounter.CounterName),
                             _app.GetRoleRowId(Guid.Parse(perfCounter.DeploymentId),perfCounter.Role),
                             new DateTime(perfCounter.EventTickCount),
                             _app.GetInstanceNumberFromFullName(perfCounter.RoleInstance),
                             float.Parse(perfCounter.CounterValue)
                         };

            _toProcessData.Rows.Add(o);

            if (_toProcessData.Rows.Count == _flushToDatabaseCount)
                WriteToProcessDataToDatabase();
        }

        private void BuildToProcessDataTable()
        {
            _toProcessData = new DataTable();

            //PerformanceCounterRowId,RoleRowId,DateTimeUTC,InstanceNumber,SampleValue

            _toProcessData.Columns.Add("PerformanceCounterRowId",typeof(int));
            _toProcessData.Columns.Add("RoleRowId",typeof(int));
            _toProcessData.Columns.Add("DateTimeUTC",typeof(DateTime));
            _toProcessData.Columns.Add("InstanceNumber",typeof(int));
            _toProcessData.Columns.Add("SampleValue",typeof(float));
        }

        private void WriteToProcessDataToDatabase()
        {
            try
            {
                if (_toProcessData != null && _toProcessData.Rows.Count > 0)
                    _app.WritePerformanceCounterBulk(_toProcessData);
            }
            catch (Exception ex)
            {
                Logger.WriteMessage(String.Format("Failed writing bulk insert cache to database.  PARAMETERS: {0}; EXCEPTION: {1}", Logger.FormatParametersToString("StorageAccountId", _storageAccountId, "StorageAccountName", _storageAccountName), ex.ToString()), EventLevel.Error, 30206);
            }
        }

        #endregion

        #region Private Methods

        private List<PerformanceCounterEntity> GetPerformanceCountersToProcess(String startingParitionKey, String startingRowKey, DateTime lastProcessedTimestampUTC, int top = 0)
        {
            IQueryable<PerformanceCounterEntity> performanceCountersTable = _tableContext.CreateQuery<PerformanceCounterEntity>(PERFORMANCE_COUNTER_TABLENAME);

            var newCounters = (from r in performanceCountersTable
                             where r.PartitionKey.CompareTo(startingParitionKey) > 0
                                & r.Timestamp > lastProcessedTimestampUTC
                             select r);

            if (top != 0)
            {
                return newCounters.Take(top).ToList();
            }

            return newCounters.ToList();

            
        }

        #endregion

        #region Deconstuctors

        void IDisposable.Dispose()
        {
            try
            {
                _app.Dispose();
            }
            catch (Exception) { } //ignore exceptions

        }

        #endregion
    }
}
